use std::{env, fs::File, iter::successors, str::FromStr, time::Instant};

use image::png::PNGEncoder;
use num::Complex;

pub fn main2() {
    println!("Hello, world!");
    let args: Vec<String> = env::args().collect();
    if args.len() <= 5 {
        eprintln!( "Usage: {} FILE PIXELS UPPERLEFT LOWERRIGHT", args[0]);
        eprintln!(" Example: {} mandul.png 1000x750 -1.20,0.35 -1,0.20", args[0]);
        std::process::exit(1);
    }
    let now = Instant::now();
    let bounds = parse_pair(&args[2], 'x').expect("error parsing image dimensions");
    let upper_left = parse_complex(&args[3]).expect("error parse upper left corner point");
    let lower_right = parse_complex(&args[4]).expect("error parse lower right corner point");
    let mut pixels = vec![0;bounds.0 * bounds.1];

    render(&mut pixels, bounds, upper_left, lower_right);

    write_image(&args[1], &pixels, bounds).expect("error writing png file");
    println!("制作图片耗时: {:?}", now.elapsed());
}


/// 尝试确定c是否属于Mandelbrot集合，最多尝试limit次
/// 
/// 如果c不属于Mandelbrot集合，则返回Some(i),其中i是c离开圆盘的迭代次数
/// 如果c可能属于Mandelbrot集合，则返回None
/// i是c离开以原点为中心的半径为2的圆盘的迭代次数。如果c似乎是集合成员之一（确切是达到了迭代限制但仍然无法证明c不是集合成员），则返回None
fn _escape_time(c:Complex<f64>, limit:usize) -> Option<usize> {
    let mut z = Complex { re: 0.0, im: 0.0 };
    for i in 0..limit {
        if z.norm_sqr() > 4.0 {
            return Some(i);
        }
        z = z* z + c;
    }
    None
}

fn escape_time2(c:Complex<f64>, limit:usize) -> Option<usize> {
    let zero = Complex {re: 0.0, im: 0.0};
    successors(Some(zero), |&z| Some(z*z + c)).take(limit).enumerate().find(|(_i,z)| z.norm_sqr() > 4.0).map(|(i,_)| i)
}

/// 把字符串s（形如‘“400x600”’或者'1.0，0.5'）解析为一个元组坐标对
/// 
/// 具体来说，s 应该具有形式<left><sep><right>，其中<sep>是分隔符字符，<left>和<right>是可以被FromStr解析的类型T
/// separator必须是acsii码表中的一个分隔符
/// 
/// 如果s具有正确的形式，则返回Some<(x,y)>。如果它不是，则返回None
fn parse_pair<T:FromStr>(s:&str, separator:char) -> Option<(T,T)> {
    match s.find(separator) {
        None => None,
        Some(index) => {
            match (T::from_str(s[..index].trim()), T::from_str(s[index+1..].trim())) {
                (Ok(l), Ok(r)) => Some((l,r)),
                _ => None
            }
        }
    }
}

/// 把一对用逗号分隔的浮点数解析为一个复数
fn parse_complex(s:&str) -> Option<Complex<f64>> {
    match parse_pair(s, ',') {
        Some((re,im)) => Some(Complex {re, im}),
        None => None
    }
}

#[test]
fn test_parse_pair(){
    assert_eq!(parse_pair::<i32>("", ','), None);
    assert_eq!(parse_pair::<i32>("1,", ','), None);
    assert_eq!(parse_pair::<i32>(",2", ','), None);
    assert_eq!(parse_pair::<i32>("1,2", ','), Some((1,2)));
    assert_eq!(parse_pair::<i32>("1,2xy", ','), None);
    assert_eq!(parse_pair::<f64>("0.2x", 'x'), None);
    assert_eq!(parse_pair::<f64>("1.25x1.25", 'x'), Some((1.25,1.25)));

}

#[test]
fn test_parse_complex() {
    assert_eq!(parse_complex("1.25,-0.0625"), Some(Complex{re:1.25, im:-0.0625}));
    assert_eq!(parse_complex(",-0.0625"), None);
}

/// 给定输出图像中像素的行与列，返回平面中对应的坐标
/// 
/// bounds是一个表示像素的宽度和高度的像素对，坐标（0,0）表示左上角的点，（bounds.0-1，bounds.1-1）表示右下角的点
/// pixel是一个表示特定像素的行与列的元组，它们必须在bounds之内
/// upper_left和lower_right参数是平面上的点，用于指定要绘制的矩形的位置
fn pixel_to_point(bounds:(usize, usize), pixel: (usize, usize), upper_left: Complex<f64>, lower_right: Complex<f64>) -> Complex<f64> {
    let (width, height) = (lower_right.re - upper_left.re, upper_left.im - lower_right.im);

    Complex {
        re: upper_left.re + pixel.0 as f64 * width / bounds.0 as f64,
        im: upper_left.im - pixel.1 as f64 * height / bounds.1 as f64
        // 因为屏幕坐标系统中的pixel.1是向下递增的。但复数的虚部是向上递增的，所以我们必须反转pixel.1的方向
    }
}

#[test]
fn test_piexl_to_point() {
    assert_eq!(pixel_to_point((100,200), (25,175), Complex {re: -1.0, im: 1.0}, Complex{re:1.0, im: -1.0}), Complex{re: -0.5, im: -0.75})
}

/// 将曼德博集对应的矩形渲染到像素缓冲区中
/// 
/// bounds是缓冲区的宽度和高度，用于像素的行和列
/// pixels包含缓冲区的像素，以行优先顺序存储。向量的长度应该是bounds.0 * bounds.1,缓冲区的每字节都包含一个像素的灰度像素
/// upper_left和lower_right参数指定要绘制的矩形的位置
fn render(pixel: &mut [u8], bounds:(usize, usize), upper_left: Complex<f64>, lower_right: Complex<f64>) {
    assert!(pixel.len() == bounds.0 * bounds.1);
    
    for row in 0..bounds.1 {
        for column in 0 .. bounds.0 {
            let point = pixel_to_point(bounds, (column, row), upper_left, lower_right);
            pixel[row *bounds.0+column] = match escape_time2(point, 255) {
                None => 0,
                Some(count) => 255 -count as u8
            }
        }
    }
}
/// 把pixel缓冲区的内容写入filename 文件中
fn write_image(filename: &str, pixel: &[u8], bounds:(usize, usize)) -> Result<(), std::io::Error> {
    let output = File::create(filename)?;

    let encoder = PNGEncoder::new(output);
    encoder.encode(pixel, bounds.0 as u32, bounds.1 as u32, image::ColorType::Gray(8))?;
    Ok(())
}
